﻿#include <ctime>
#include <array>
#ifndef _WIN32
#include <sys/time.h>
#endif

/* 
 * 这是一个安全版本的 localtime() 函数，不包含锁且对 fork() 友好。
 * 即使是 localtime() 的 _r 版本在 Redis 中也不能安全使用。
 * 当主线程 fork() 时，另一个线程可能正在调用 localtime()。
 * 之后，当子进程再次调用 localtime() 时（例如为了向 Redis 日志记录内容），
 * 可能会发生死锁：在 fork 进程的地址空间副本中，锁永远不会被释放。
 *
 * 此函数将时区 'tz' 作为参数，'dst' 标志用于检查夏令时是否当前生效。
 * 此函数的调用者应尽快在 main() 函数中调用 tzset() 以从全局变量 'timezone' 获取时区偏移量。
 * 要获取夏令时信息（是否当前活动），一个技巧是尽快在 main() 中调用 localtime()，
 * 并从 tm 结构的 tm_isdst 字段获取信息。
 * 但是，对于长时间运行的进程，夏令时可能会在将来切换，因此应在安全时间刷新此信息。
 *
 * 请注意，此函数不适用于 1/1/1970 之前的日期，它仅设计用于处理 time(NULL) 可能返回的内容，
 * 并支持 Redis 记录日期，它并不是一个完整的实现。
 */
namespace toolkit {
static int _daylightActive;
static long _currentTimezone;

int getDaylightActive() {
    return _daylightActive;
}

static bool isLeapYear(time_t year) {
    if (year % 4)
        return false; // 不能被4整除的年份不是闰年
    else if (year % 100)
        return true;  // 能被4整除但不能被100整除的是闰年
    else if (year % 400)
        return false; // 能被100整除但不能被400整除的不是闰年
    else
        return true;  // 能被100和400整除的是闰年
}

void noLocksLocaltime(struct tm *tmp, time_t t) {
    constexpr time_t secsMin = 60;
    constexpr time_t secsHour = 3600;
    constexpr time_t secsDay = 3600 * 24;

    t -= _currentTimezone;                // 调整时区
    t += 3600 * getDaylightActive();      // 调整夏令时
    time_t days = t / secsDay;            // 自纪元以来经过的天数
    time_t seconds = t % secsDay;         // 剩余秒数

    tmp->tm_isdst = getDaylightActive();
    tmp->tm_hour = seconds / secsHour;
    tmp->tm_min = (seconds % secsHour) / secsMin;
    tmp->tm_sec = (seconds % secsHour) % secsMin;
#ifndef _WIN32
    tmp->tm_gmtoff = -_currentTimezone;
#endif
    // 1/1/1970是星期四，即从tm结构的角度看是第4天
    // 其中星期日=0，因此要计算星期几，我们必须加4并对7取模
    tmp->tm_wday = (days + 4) % 7;

    // 计算当前年份
    tmp->tm_year = 1970;
    while (true) {
        // 闰年多一天
        time_t daysThisYear = 365 + isLeapYear(tmp->tm_year);
        if (daysThisYear > days)
            break;
        days -= daysThisYear;
        tmp->tm_year++;
    }
    tmp->tm_yday = days; // 当年的第几天

    // 我们需要计算当前是哪个月和月中的哪一天
    // 为此，我们需要根据每个月的天数跳过天数，并为闰年的2月调整多一天
    std::array<int, 12> mdays = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    mdays[1] += isLeapYear(tmp->tm_year);

    tmp->tm_mon = 0;
    while (days >= mdays[tmp->tm_mon]) {
        days -= mdays[tmp->tm_mon];
        tmp->tm_mon++;
    }

    tmp->tm_mday = days + 1;    // 加1因为我们的'days'是从0开始的
    tmp->tm_year -= 1900;       // 令人惊讶的是tm_year是年份-1900
}

void localTimeInit() {
    // 获取时区和夏令时信息
    tzset(); // 现在全局变量'timezone'已填充
#if defined(__linux__) || defined(__sun)
    _currentTimezone = timezone;
#elif defined(_WIN32)
    time_t timeUtc;
    struct tm tmLocal;

    // 获取UTC时间
    time(&timeUtc);

    // 获取本地时间
    // 对于Linux使用localtime_r以确保线程安全
    //localtime_r(&timeUtc, &tmLocal);
    localtime_s(&tmLocal, &timeUtc);

    time_t timeLocal;
    struct tm tmGmt;

    // 将tm转换为time_t
    timeLocal = mktime(&tmLocal);

    // 转换为GMT tm
    //gmtime_r(&timeUtc, &tmGmt);//linux
    gmtime_s(&tmGmt, &timeUtc);

    int timeZone = tmLocal.tm_hour - tmGmt.tm_hour;
    if (timeZone < -12) {
        timeZone += 24;
    }
    else if (timeZone > 12) {
        timeZone -= 24;
    }

    _currentTimezone = timeZone * 3600; // 转换为秒
#else
    struct timeval tv;
    struct timezone tz;
    gettimeofday(&tv, &tz);
    _currentTimezone = tz.tz_minuteswest * 60L;
#endif
    time_t t = time(nullptr);
    struct tm *aux = localtime(&t);
    _daylightActive = aux->tm_isdst;
}
} // namespace toolkit
